import { Injectable, Optional } from '@angular/core';
// import { Headers, Http, Response } from '@angular/http';

import {
    HttpClient,
    HttpHeaders,
    HttpResponse,
    HttpErrorResponse,
    HttpParams,
    HttpRequest,
    HttpEventType
} from '@angular/common/http';

import { Observable } from 'rxjs';

import { Hero } from './hero.model';

@Injectable()
export class HeroService {
    // URL to web api
    private heroesUrl = 'api/heroes';
    private headers = new Headers({ 'Content-Type': 'application/json' });

    constructor(
        // private http: Http,
        private httpClient: HttpClient
    ) { }

    /**
     * Just the DEMO code. DO NOT INVOKE THIS FUNCTION UNLESS YOU ARE EXPECTING ERRORS.
     */
    httpClientDemo(): void {
        // HttpClient not supporting in memory web api

        // GET
        this.httpClient.get<Hero[]>('http://localhost:3000/app/heroes/shared/data.json')
            // .retry(3)   // Retry this request up to 3 times.
            .subscribe((data: Hero[]) => {
                console.log(`httpClient: ${data}`);
            }, (err: HttpErrorResponse) => {
                if (err.error instanceof Error) {
                    // A client-side or network error occurred. Handle it accordingly.
                    console.log('An error occurred:', err.error.message);
                } else {
                    // The backend returned an unsuccessful response code.
                    // The response body may contain clues as to what went wrong,
                    console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
                }
            });

        // Not all APIs return JSON data. Suppose you want to read a text file on the server. 
        // You have to tell HttpClient that you expect a textual response:
        this.httpClient
            .get('/textfile.txt', { responseType: 'text' })
            // The Observable returned by get() is of type Observable<string>
            // because a text response was specified. There's no need to pass
            // a <string> type parameter to get().
            .subscribe(data => console.log(data));

        // POST
        const body = { name: 'Brad' };

        this.httpClient
            .post('/api/developers/add', body)
            // See below - subscribe() is still necessary when using post().
            .subscribe();

        // Note the subscribe() method. All Observables returned from HttpClient are cold, which is to say 
        // that they are blueprints for making requests. Nothing will happen until you call subscribe(), 
        // and every such call will make a separate request. For example, this code sends a POST request 
        // with the same data twice:

        // 0 requests made - .subscribe() not called.
        const req = this.httpClient.post('/api/items/add', body);

        // 1 request made.
        req.subscribe();

        // 2 requests made.
        req.subscribe();

        // Configuring other parts of the request
        // Headers
        this.httpClient
            .post('/api/items/add', body, {
                headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
            })
            .subscribe();

        // URL Parameters
        // In this way, you send the POST request to the URL /api/items/add?id=3
        this.httpClient
            .post('/api/items/add', body, {
                params: new HttpParams().set('id', '3'),
            })
            .subscribe();

        // Listening to progress events
        // const request = new HttpRequest('POST', '/upload/file', file, {
        //     reportProgress: true,
        // });

        // This option enables tracking of progress events. Remember, every progress event triggers change 
        // detection, so only turn them on if you intend to actually update the UI on each event.

        // Next, make the request through the request() method of HttpClient. The result will be an Observable 
        // of events, just like with interceptors:

        // this.httpClient.request(req).subscribe((event: any) => {
        //     // Via this API, you get access to the raw event stream.
        //     // Look for upload progress events.
        //     if (event.type === HttpEventType.UploadProgress) {
        //         // This is an upload progress event. Compute and show the % done:
        //         const percentDone = Math.round(100 * event.loaded / event.total);
        //         console.log(`File is ${percentDone}% uploaded.`);
        //     } else if (event instanceof HttpResponse) {
        //         console.log('File is completely uploaded!');
        //     }
        // });
    }

    // getHero(id: number): Promise<Hero> {
    //     return this.getHeroes().then(heroes => heroes.find(hero => hero.id === id));
    // }

    // getHero(id: number | string): Promise<Hero> | Observable<Hero> {
    //     // +id
    //     // (+) before `id` turns the string into a number

    //     const url = `${this.heroesUrl}/${id}`;

    //     return this.http.get(url)
    //         .toPromise()
    //         .then(response => response.json().data as Hero)
    //         .catch(this.handleError);
    // }

    // async getHeroesAsync(): Promise<Hero[]> {
    //     let response = await this.http.get(this.heroesUrl).toPromise();
    //     let data = response.json().data as Hero[];

    //     return data;
    // }

    /**
     * Gets the hero data from remote server
     */
    // getHeroes(): Promise<Hero[]> {
    //     // return Promise.resolve(HEROES);

    //     return this.http.get(this.heroesUrl)
    //         .toPromise()
    //         .then(response => response.json().data as Hero[])
    //         // .then(response => { return response.json().data as Hero[] })
    //         // .then(this.returnHeroes)
    //         .catch(this.handleError);
    // }

    // getHeroesSlowly(): Promise<Hero[]> {
    //     return new Promise(resolve => {
    //         // Simulate server latency with 2 second delay
    //         setTimeout(() => resolve(this.getHeroes()), 2000);
    //     });
    // }

    // create(name: string): Promise<Hero> {
    //     return this.http
    //         .post(this.heroesUrl, JSON.stringify({ name: name }), { headers: this.headers })
    //         .toPromise()
    //         .then(res => res.json().data)
    //         .catch(this.handleError);
    // }

    // update(hero: Hero): Promise<Hero> {
    //     const url = `${this.heroesUrl}/${hero.id}`;

    //     return this.http
    //         .put(url, JSON.stringify(hero), { headers: this.headers })
    //         .toPromise()
    //         .then(() => hero)
    //         .catch(this.handleError);
    // }

    // delete(id: number): Promise<void> {
    //     const url = `${this.heroesUrl}/${id}`;

    //     return this.http.delete(url, { headers: this.headers })
    //         .toPromise()
    //         .then(() => null)
    //         .catch(this.handleError);
    // }

    /**
     * Used by ngFor directive.
     * 
     * Why?
     * *ngFor with trackBy
     * The NgForOf directive may perform poorly, especially with large lists. A small change to one item, 
     * an item removed, or an item added can trigger a cascade of DOM manipulations.

     * For example, re-querying the server could reset the list with all new hero objects.

     * Most, if not all, are previously displayed heroes. You know this because the id of each hero hasn't 
     * changed. But Angular sees only a fresh list of new object references. It has no choice but to tear 
     * down the old DOM elements and insert all new DOM elements.

     * Angular can avoid this churn with trackBy. Add a method to the component that returns the value 
     * NgForOf should track. In this case, that value is the hero's id.
     */
    trackByHeroes(index: number, hero: Hero): number {
        return hero.id
    }

    // getHeroes2(callback: (heroes: Hero[]) => void): void {
    //     callback(HEROES);
    // }

    // private returnHeroes(response: Response): Promise<Hero[]> {
    //     return Promise.resolve(response.json().data as Hero[]);
    // }

    private handleError(error: any): Promise<any> {
        // For DEMO purpose ONLY
        // console.error('An error occured', error);

        // TODO: When bad things happen and reaches here, 'this' is undefined. Why?
        // if (this.logger) {
        //     this.logger.error(error);
        // }

        return Promise.reject(error.message || error);
    }
}